package com.dlc.shop.security.api.controller;

import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dlc.shop.bean.enums.SendType;
import com.dlc.shop.bean.model.User;
import com.dlc.shop.common.constants.OauthCacheNames;
import com.dlc.shop.common.enums.SysTypeEnum;
import com.dlc.shop.common.exception.YamiShopBindException;
import com.dlc.shop.common.response.ResponseEnum;
import com.dlc.shop.common.response.ServerResponseEntity;
import com.dlc.shop.common.util.PrincipalUtil;
import com.dlc.shop.common.util.RedisUtil;
import com.dlc.shop.config.WxConfig;
import com.dlc.shop.dao.UserMapper;
import com.dlc.shop.security.api.dto.MaCodeAuthenticationDTO;
import com.dlc.shop.security.api.dto.SocialAuthenticationDTO;
import com.dlc.shop.security.common.bo.LoginInfoBO;
import com.dlc.shop.security.common.bo.UidInfoBO;
import com.dlc.shop.security.common.dao.AppConnectMapper;
import com.dlc.shop.security.common.dto.AuthenticationDTO;
import com.dlc.shop.security.common.enums.SocialType;
import com.dlc.shop.security.common.manager.PasswordCheckManager;
import com.dlc.shop.security.common.manager.PasswordManager;
import com.dlc.shop.security.common.manager.TokenStore;
import com.dlc.shop.security.common.model.AppConnect;
import com.dlc.shop.security.common.service.AppConnectService;
import com.dlc.shop.security.common.vo.TokenInfoVO;
import com.dlc.shop.service.SmsLogService;
import com.dlc.shop.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import me.chanjar.weixin.common.error.WxErrorException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.Objects;

/**
 * @author FrozenWatermelon
 * @date 2020/6/30
 */
@RestController
@Tag(name = "登录")
@AllArgsConstructor
public class LoginController {

    private final TokenStore tokenStore;
    private final UserMapper userMapper;
    private final AppConnectService appConnectService;
    private final PasswordCheckManager passwordCheckManager;
    private final SmsLogService smsLogService;
    private final PasswordManager passwordManager;
    private final AppConnectMapper appConnectMapper;
    private final WxConfig wxConfig;
    private final UserService userService;


    @PostMapping("/login")
    @Operation(summary = "账号密码(用于h5、pc登录)" , description = "通过账号/手机号/用户名密码登录，还要携带用户的类型，也就是用户所在的系统")
    public ServerResponseEntity<TokenInfoVO> login(
            @Valid @RequestBody AuthenticationDTO authenticationDTO) {
        String mobileOrUserName = authenticationDTO.getUserName();
        User user = getUser(mobileOrUserName);
        String decryptPassword = passwordManager.decryptPassword(authenticationDTO.getPassWord());

        // 半小时内密码输入错误十次，已限制登录30分钟
        passwordCheckManager.checkPassword(SysTypeEnum.ORDINARY,user.getUserMobile(),decryptPassword, user.getLoginPassword());

        return ServerResponseEntity.success(getTokenInfoVO(user, null));
    }

    @PostMapping("/wx/login")
    @Operation(summary = "账号密码登录(用于微信小程序和公众号)" , description = "微信小程序和公众号通过账号/手机号/用户名密码登录, 二合一登录（包含绑定 + 登录）")
    public ServerResponseEntity<TokenInfoVO> wxLogin(
            @Valid @RequestBody SocialAuthenticationDTO authenticationDTO) {
        if (StrUtil.isBlank(authenticationDTO.getTempUid())) {
            return ServerResponseEntity.showFailMsg("tempUid is blank");
        }
        User user = getUser(authenticationDTO.getUserName());
        String decryptPassword = passwordManager.decryptPassword(authenticationDTO.getPassWord());
        // 半小时内密码输入错误十次，已限制登录30分钟
        passwordCheckManager.checkPassword(SysTypeEnum.ORDINARY,user.getUserMobile(),decryptPassword, user.getLoginPassword());

        AppConnect appConnect = checkAndGetAppConnect(user.getUserId(), authenticationDTO.getSocialType(), authenticationDTO.getTempUid());

        return ServerResponseEntity.success(getTokenInfoVO(user, appConnect));
    }


    @PostMapping("/ma/login")
    @Operation(summary = "微信小程序一键登录" , description = "三合一登录（包含注册 + 绑定 + 登录）")
    public ServerResponseEntity<TokenInfoVO> maLogin(@Valid @RequestBody MaCodeAuthenticationDTO maCodeAuthenticationDTO) {
        WxMaPhoneNumberInfo newPhoneNoInfo;
        try {
            newPhoneNoInfo = wxConfig.getWxMaService().getUserService().getPhoneNoInfo(maCodeAuthenticationDTO.getCode());
        } catch (WxErrorException e) {
            throw new YamiShopBindException(e.getMessage());
        }
        // 没有区号的手机号，国外手机号会有区号
        String mobile = newPhoneNoInfo.getPurePhoneNumber();

        return threeInOneLogin(mobile, maCodeAuthenticationDTO.getTempUid(), SocialType.MA.value());
    }


    @PostMapping("/smsLogin")
    @Operation(summary = "短信登录" , description = "三合一登录（包含注册 + 绑定 + 登录）")
    public ServerResponseEntity<TokenInfoVO> smsLogin(
            @Valid @RequestBody SocialAuthenticationDTO authenticationDTO) {

        // 非验证码登录
        boolean notLoginByCode = StrUtil.isBlank(authenticationDTO.getTempUid())
                && (Objects.equals(authenticationDTO.getSocialType(), SocialType.MA.value())
                || Objects.equals(authenticationDTO.getSocialType(), SocialType.MP.value()));
        if (notLoginByCode) {
            return ServerResponseEntity.showFailMsg("tempUid is blank");
        }
        // 验证码登录
        boolean validCode;
        try {
            validCode = smsLogService.checkValidCode(authenticationDTO.getUserName(), String.valueOf(authenticationDTO.getPassWord()), SendType.CAPTCHA);
        } catch (YamiShopBindException e) {
            // 验证码校验过频繁，请稍后再试
            throw new YamiShopBindException("yami.user.code.check.too.much");
        }
        if (!validCode) {
            // 验证码有误或已过期
            throw new YamiShopBindException("yami.user.code.error");
        }
        // 如果没有注册的话，短信登录将会进行注册
        // 在pc/小程序/公众号的登录，都有短信登录的方法。但是公众号/小程序的短信登录，登录了之后会见这个用户和公众号/小程序绑定一起（登录并绑定）
        ServerResponseEntity<TokenInfoVO> tokenInfoVOServerResponseEntity = threeInOneLogin(authenticationDTO.getUserName(), authenticationDTO.getTempUid(), authenticationDTO.getSocialType());

        //将微信跟手机号进行绑定
        if (authenticationDTO.getOpenId()!=null){
            User user = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getUserMobile, authenticationDTO.getUserName()));
            if(user.getNickName().contains("u")&& user.getPic()==null){
                user.setPic(authenticationDTO.getHeadimgurl());
                user.setNickName(authenticationDTO.getNickname());
            }
            user.setWxOpenId(authenticationDTO.getOpenId());
            userService.updateById(user);
        }
        return tokenInfoVOServerResponseEntity;
    }


    private TokenInfoVO getTokenInfoVO(User user, AppConnect appConnect) {
        if (user.getStatus() == 0) {
            throw new YamiShopBindException("yami.user.disabled");
        }
        // 登录返回token
        TokenInfoVO tokenInfoVO = tokenStore.storeAndGetVo(new UidInfoBO(SysTypeEnum.ORDINARY, user.getUserId()));
        if (!Objects.isNull(appConnect)) {
            // 缓存当前登录的openId
            LoginInfoBO loginInfoBO = new LoginInfoBO();
            loginInfoBO.setSocialType(appConnect.getAppId());
            loginInfoBO.setBizUserId(appConnect.getBizUserId());
            RedisUtil.set(OauthCacheNames.LOGIN_INFO + tokenInfoVO.getAccessToken(),
                    JSONObject.toJSONString(loginInfoBO),
                    tokenInfoVO.getExpiresIn());
        }
        return tokenInfoVO;
    }

    /**
     * 三合一登录（包含注册 + 绑定 + 登录）
     */
    private ServerResponseEntity<TokenInfoVO> threeInOneLogin(String mobile,String tempUid, Integer socialType) {
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserMobile, mobile));
        AppConnect appConnect = null;
        // 没有就注册
        if (user == null) {
            if (Objects.equals(socialType,SocialType.MA.value()) || Objects.equals(socialType,SocialType.MP.value())) {
                appConnect = appConnectService.getByTempUid(tempUid);
                if (appConnect == null || appConnect.getUserId() != null) {
                    return ServerResponseEntity.fail(ResponseEnum.TEMP_UID_ERROR);
                }
            }
            // 注册并绑定用户
            user = appConnectService.registerAndBindUser(mobile, null, tempUid);
        } else  {
            appConnect = checkAndGetAppConnect(user.getUserId(), socialType, tempUid);
        }
        // 绑定
        return ServerResponseEntity.success(getTokenInfoVO(user, appConnect));
    }

    /**
     * 登录并绑定用户
     */
    private AppConnect checkAndGetAppConnect(String userId, Integer socialType,String tempUid) {
        // 只有微信公众号和小程序的登录会进行登录并绑定的操作
        if (!Objects.equals(socialType,SocialType.MA.value()) && !Objects.equals(socialType,SocialType.MP.value())) {
            return null;
        }
        // 在SocialLoginController 当中，会返回一个tempUid用来换取openid的
        AppConnect appConnect = appConnectService.getByTempUid(tempUid);
        if (appConnect == null) {
            throw new YamiShopBindException(ResponseEnum.TEMP_UID_ERROR);
        }
        // 二合一登录，啥意思呢？
        // 1. 绑定：将该账号和该微信的openId进行绑定
        // 2. 登录：返回token登录成功
        if (appConnect.getUserId() == null) {
            appConnectMapper.bindUserIdByTempUid(userId, appConnect.getTempUid());
        }
        // 如果有userId就判断下是不是一样的user，不是的话，不能够绑定
        else if (!Objects.equals(appConnect.getUserId(), userId)) {
            throw new YamiShopBindException(ResponseEnum.SOCIAL_ACCOUNT_BIND_BY_OTHER);
        }
        return appConnect;
    }

    private User getUser(String mobileOrUserName) {
        User user = null;
        // 手机验证码登陆，或传过来的账号很像手机号
        if (PrincipalUtil.isMobile(mobileOrUserName)) {
            user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserMobile, mobileOrUserName));
        }
        // 如果不是手机验证码登陆， 找不到手机号就找用户名
        if  (PrincipalUtil.isUserName(mobileOrUserName)) {
            user = userMapper.selectOneByUserName(mobileOrUserName);
        }
        // 如果不是手机验证码登陆， 找不到手机号就找用户名
        if  (user == null) {
            user = userMapper.selectOneByUserName(mobileOrUserName);
        }
        if (user == null) {
            throw new YamiShopBindException("yami.user.account.error");
        }
        return user;
    }



}
